package com.zpself.module.多线程.volatile2;

import org.junit.Test;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author By ZengPeng
 * @Description 被volatile修饰的变量能够保证每个线程能够获取该变量的最新值，从而避免出现数据脏读的现象。
 *              将当前处理器缓存行的数据写回系统内存；
 *              这个写回内存的操作会使得其他CPU里缓存了该内存地址的数据无效
 *
 *     如果对声明了volatile的变量进行  写操作 ，JVM就会向处理器发送一条Lock前缀的指令，将这个变量所在缓存行的数据写回到系统内存
 *              但是，就算写回到内存，如果其他处理器缓存的值还是旧的,再执行计算操作就会有问题。
 *              所以，在多处理器下，为了保证各个处理器的缓存是一致的，就会实现缓存一致性协议，
 *              每个处理器通过  【嗅探】 在【总线上传播的数据】   来【检查自己缓存】的值是不是过期了  ---【这里很重要】
 *              当处理器发现自己缓存行对应的内存地址被修改，就会将当前处理器的缓存行设置成无效状态，
 *
 *              当处理器对这个数据进行修改操作的时候，会重新从系统内存中把数据读到处理器缓存里。(仍然会有问题，如下)
 *
 *
 *     happens-before
 * @date in  2021/3/3 19:51
 * @Modified By
 */
public class Volatile实现累加 {
    private static volatile int i = 0;

    @Test
    public void add() throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        final CountDownLatch readying = new CountDownLatch(1);//线程准备
        final CountDownLatch over = new CountDownLatch(10);//线程跑完了  ，统计耗时

        for (int j = 0; j < 10; j++) {
            executorService.execute(() -> {
                try {
                    readying.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程:" + Thread.currentThread().getName() + "开始工作了！");
                for (int k = 0; k < 100000; k++) {
                    //读取、改、赋值3步组成。
                    // 线程1改为：  2之后，正要赋值.
                    // 线程2赋值内存：  2
                    // 线程1中i 被知为 i=2 ，但线程1无法再执行++ 。随后将 i=2完成赋值  i=2
                    //此时漏加1
                    i++;
                }
                over.countDown();
            });
        }

        System.out.println("---------------准备---------------");

        System.out.println("---------------开始---------------");
        readying.countDown();
        long start = System.currentTimeMillis();

        over.await();
        System.out.println("---------------结束---------------");

        System.out.println("输出i = " + i);
        System.out.println("耗时 = " + (System.currentTimeMillis() - start));
    }
}